package com.ylr.redis.framework.service;

import com.ylr.redis.common.exception.RedisException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-08-11 09:28:00
 * className: RedisListUtils Redis-List数据结构操作工具类
 * version: 1.0
 * description:
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisListService {

    /*
        注意：
          1、所有向List数据结构添加数据操作，值类型统一采用‘泛型’设计，且同一个List数据结构存储的‘所有值’最好保持‘类型统一’，需‘开发者’确保所有‘值’的类型统一
            1.1、由于Redis的key-value特性，所有‘添加值’的跟随请求发送至Redis前都将进行‘序列化’操作，则在Redis不会再做类型校验，统一以‘字符串’的方式存储，
                 如果不能保证同一个List结构存储的所有值类型统一，则程序‘获取值’时进行‘反序列化’操作，可能会引发‘类型转换异常’，产生重大事故。
            1.2、如果同一个List结构存储的值类型不一致，管理上也会增加额外的难度
          2、TODO 仍待解决当leftPop和rightPop操作时，Redis返回值类型和泛型类型不一致，程序报异常，Redis没有回滚
          3、TODO 仍待解决当rangeReverse操作时，下标传入优化
     */

    private static final Logger log = LoggerFactory.getLogger(RedisListService.class);

    /**
     * Redis-List数据结构操作模板对象
     */
    private final ListOperations listOperations;

    private final RedisTemplate redisTemplate;

    /**
     * 定点删除采用‘删除标记位’的方式进行
     * 1、将待删除下标的元素替换成删除标记位
     * 2、删除标记位，完成定点删除
     * 采用顶点删除可以删除List数据结构任意位置的元素
     * 采用List操作模板对象的remove删除方式，如果存在多个相同的值，则会将全部值删除
     */
//    @Value("${spring.redis.list.deleteFlag}")   // 如果需要动态手动配置删除标记位，则可以在配置文件中添加，并取消注释即可
    private final String deleteFlag = "deleteFlag";

    @Autowired
    public RedisListService(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.listOperations = redisTemplate.opsForList();
    }

    /**
     * 操作listKey数据，在其‘头部’添加一条数据
     * @param listKey List空间键
     * @param value   添加的值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long leftPush(final String listKey, final T value) {
        log.debug("List类型 leftPush操作 listKey={} value={}", listKey, value);
        return listOperations.leftPush(listKey, value);
    }

    /**
     * 操作listKey数据，在pivotValue值‘之前’添加一条数据
     * 如果‘基准值’pivotValue在listKey中不存在，则插入失败，返回-1
     * @param listKey    List空间键
     * @param pivotValue 基准位置的值（在这个值的前面插入value值）
     * @param value      插入值
     * @param <T>        泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long leftPush(final String listKey, final T pivotValue, final T value) {
        log.debug("List类型 insert-before操作 listKey={} pivotValue={} value={}", listKey, pivotValue, value);
        return listOperations.leftPush(listKey, pivotValue, value);
    }

    /**
     * 操作listKey数据，一次性添加多条数据，依次向其头部添加数据
     * @param listKey List空间键
     * @param values  添加的所有值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long leftPushAll(final String listKey, final T... values) {
        log.debug("List类型 leftPushAll操作 listKey={} values={}", listKey, Arrays.toString(values));
        return listOperations.leftPushAll(listKey, values);
    }

    /**
     * 操作listKey数据，一次性添加多条数据，依次向其头部添加数据
     * @param listKey List空间键
     * @param values  添加的所有值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long leftPushAll(final String listKey, final Collection<T> values) {
        log.debug("List类型 leftPushAll操作 listKey={} values={}", listKey, values);
        return listOperations.leftPushAll(listKey, values);
    }

    /**
     * 操作listKey数据，在其‘头部’添加一条数据，当listKey存在时，才向其添加
     * 如果listKey不存在，操作返回0，表示操作失败
     * @param listKey List空间键
     * @param value   添加的值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long leftPushIfPresent(final String listKey, final T value) {
        log.debug("List类型 leftPushX操作 listKey={} value={}", listKey, value);
        return listOperations.leftPushIfPresent(listKey, value);
    }

    /**
     * 操作listKey数据，在其‘尾部’添加一条数据
     * @param listKey List空间键
     * @param value   添加的值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long rightPush(final String listKey, final T value) {
        log.debug("List类型 rightPush操作 listKey={} value={}", listKey, value);
        return listOperations.rightPush(listKey, value);
    }

    /**
     * 操作listKey数据，在pivotValue值‘之后’添加一条数据
     * 如果‘基准值’pivotValue在listKey中不存在，则插入失败，返回-1
     * @param listKey    List空间键
     * @param pivotValue 基准位置的值（在这个值的后面插入value值）
     * @param value      插入值
     * @param <T>        泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long rightPush(final String listKey, final T pivotValue, final T value) {
        log.debug("List类型 insert-after操作 listKey={} pivotValue={} value={}", listKey, pivotValue, value);
        return listOperations.rightPush(listKey, pivotValue, value);
    }

    /**
     * 操作listKey数据，一次性添加多条数据，依次向其’尾部‘添加数据
     * @param listKey List空间键
     * @param values  添加的所有值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long rightPushAll(final String listKey, final T... values) {
        log.debug("List类型 leftPushAll操作 listKey={} values={}", listKey, Arrays.toString(values));
        return listOperations.rightPushAll(listKey, values);
    }

    /**
     * 操作listKey数据，一次性添加多条数据，依次向其‘尾部’添加数据
     * @param listKey List空间键
     * @param values  添加的所有值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long rightPushAll(final String listKey, final Collection<T> values) {
        log.debug("List类型 leftPushAll操作 listKey={} values={}", listKey, values);
        return listOperations.rightPushAll(listKey, values);
    }

    /**
     * 操作listKey数据，在其‘尾部’添加一条数据，当listKey存在时，才向其添加
     * 如果listKey不存在，操作返回0，表示操作失败
     * @param listKey List空间键
     * @param value   添加的值
     * @param <T>     泛型类型
     * @return 数据添加完成后，返回listKey的元素个数
     */
    public <T> Long rightPushIfPresent(final String listKey, final T value) {
        log.debug("List类型 leftPushX操作 listKey={} value={}", listKey, value);
        return listOperations.rightPushIfPresent(listKey, value);
    }

    /**
     * 操作listKey数据，获取并删除其‘头部’元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     * @param listKey List空间键
     * @param <T>     泛型类型
     * @return listKey头部第一个元素值
     */
    public <T> T leftPop(final String listKey) {
        log.debug("List类型 leftPop操作 listKey={}", listKey);
        return (T) listOperations.leftPop(listKey);
    }

    /**
     * 操作listKey数据，获取并删除其‘头部’元素。当listKey没有元素时，等待时长time，如果期间listKey新增元素，则返回头部元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     *  3、如果超过等待时长time，任然没有元素返回，则报超时异常，返回null
     * @param listKey List空间键
     * @param time    等待时长
     * @param <T>     泛型类型
     * @return listKey头部第一个元素值
     */
    public <T> T leftPop(final String listKey, final long time) {
        return this.leftPop(listKey, time, TimeUnit.SECONDS);
    }

    /**
     * 操作listKey数据，获取并删除其‘头部’元素。当listKey没有元素时，等待时长time，如果期间listKey新增元素，则返回头部元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     *  3、如果超过等待时长time，任然没有元素返回，则报超时异常，返回null
     * @param listKey  List空间键
     * @param time     等待时长
     * @param timeUnit 时间类型
     * @param <T>      泛型类型
     * @return listKey头部第一个元素值
     */
    public <T> T leftPop(final String listKey, final long time, final TimeUnit timeUnit) {
        log.debug("List类型 leftPop操作 listKey={} time={} timeUnit={}", listKey, time, timeUnit);
        try {
            return (T) listOperations.leftPop(listKey, time, timeUnit);
        } catch (Exception e) {
            log.error("List类型 leftPop操作异常 listKey={} time={} timeUnit={} 异常消息={}", listKey, time, timeUnit, e.getMessage());
        }
        return null;
    }

    /**
     * 操作listKey数据，获取并删除其‘尾部’元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     * @param listKey List空间键
     * @param <T>     泛型类型
     * @return listKey尾部第一个元素值
     */
    public <T> T rightPop(final String listKey) {
        log.debug("List类型 rightPop操作 listKey={}", listKey);
        return (T) listOperations.rightPop(listKey);
    }

    /**
     * 操作listKey数据，获取并删除其‘尾部’元素。当listKey没有元素时，等待时长time，如果期间listKey新增元素，则返回尾部元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     *  3、如果超过等待时长time，任然没有元素返回，则报超时异常，返回null
     * @param listKey List空间键
     * @param time     等待时长
     * @param <T>     泛型类型
     * @return listKey头部第一个元素值
     */
    public <T> T rightPop(final String listKey, final long time) {
        return this.rightPop(listKey, time, TimeUnit.SECONDS);
    }

    /**
     * 操作listKey数据，获取并删除其‘尾部’元素。当listKey没有元素时，等待时长time，如果期间listKey新增元素，则返回尾部元素
     * 注意：
     *  1、如果Redis返回值类型和泛型类型不一致，则报类型转换异常
     *  2、如果listKey不存在，则返回null
     *  3、如果超过等待时长time，任然没有元素返回，则报超时异常，返回null
     * @param listKey  List空间键
     * @param time     等待时长
     * @param timeUnit 时间类型
     * @param <T>      泛型类型
     * @return listKey头部第一个元素值
     */
    public <T> T rightPop(final String listKey, final long time, final TimeUnit timeUnit) {
        log.debug("List类型 rightPop操作 listKey={} time={} timeUnit={}", listKey, time, timeUnit);
        try {
            return (T) listOperations.rightPop(listKey, time, timeUnit);
        } catch (Exception e) {
            log.error("List类型 rightPop操作异常 listKey={} time={} timeUnit={} 异常消息={}", listKey, time, timeUnit, e.getMessage());
        }
        return null;
    }

    /**
     * 操作listKey数据，获取下标index对应的值
     * 注意：
     *  1、如果下标index的值大于listKey的元素个数，则返回null
     *  2、如果下标index对应的值根据返回值类型不一致，则报类型转换异常
     *  3、如果index是负数，则代表从后向前查找对应index位置的值，如-1是最后一位，-2是倒数第二位。同时满足注意1的条件。
     * @param listKey List空间键
     * @param index   查找下标值
     * @param <T>     泛型类型
     * @return listKey结构中下标index对应的值
     */
    public <T> T index(final String listKey, final long index) {
        log.debug("List类型 index操作 listKey={} index={}", listKey, index);
        return (T) listOperations.index(listKey, index);
    }

    /**
     * 操作listKey数据，获取值value在listKey中的下标
     * 注意：
     *  1、如果值value在listKey中不存在，则返回null（纯数字的值，区分数据类型，如Integer类型的数字不能匹配String类型的数字，反之同理）
     *  2、如果value在listKey中存在多个值，返回第一个值所在的下标（从左往右遍历）
     * @param listKey List空间键
     * @param value   查找的值
     * @param <T>     泛型类型
     * @return 值value在listKey中的下标
     */
    public <T> Long indexOf(final String listKey, final T value) {
        log.debug("List类型 indexOf操作 listKey={} value={}", listKey, value);
        return listOperations.indexOf(listKey, value);
    }

    /**
     * 操作listKey数据，获取值value在listKey中的下标
     * 注意：
     *  1、如果值value在listKey中不存在，则返回null（纯数字的值，区分数据类型，如Integer类型的数字不能匹配String类型的数字，反之同理）
     *  2、如果value在listKey中存在多个值，返回最后一个值所在的下标（从右往左遍历）
     * @param listKey List空间键
     * @param value   查找的值
     * @param <T>     泛型类型
     * @return 值value在listKey中的下标
     */
    public <T> Long lastIndexOf(final String listKey, final T value) {
        log.debug("List类型 lastIndexOf操作 listKey={} value={}", listKey, value);
        return listOperations.lastIndexOf(listKey, value);
    }

    /**
     * 操作listKey数据，更新下标index出的值为value
     * 注意：
     *  1、如果下标index值不是listKey的有效范围，则报异常（如，index为正值，但大于listKey的元素个数；index为负值，但小于listKey的元素个数取反值）
     * @param listKey List空间键
     * @param index   更新下标
     * @param value   最新值
     * @param <T>     泛型类型
     */
    public <T> void set(final String listKey, final long index, final T value) {
        log.debug("List类型 set操作 listKey={} index={} value={}", listKey, index, value);
        try {
            listOperations.set(listKey, index, value);
        } catch (Exception e) {
            log.error("List类型 set操作异常 listKey={} index={} value={} 异常信息={}", listKey, index, value, e.getMessage());
        }
    }

    /**
     * 操作listKey数据，返回listKey的元素个数
     * 注意：
     *  1、如果listKey不存在，则返回0
     * @param listKey List空间键
     * @return listKey的元素个数
     */
    public Long size(final String listKey) {
        log.debug("List类型 size操作 listKey={}", listKey);
        return listOperations.size(listKey);
    }

    /**
     * 操作listKey数据，分页截取listKey的元素，从左往右截取元素
     * 注意：
     *  1、截取元素包含开始下标start对应的元素
     *  2、截取元素不包含结束下标end对应的元素
     *  3、截取原则为‘左闭右开’
     * @param listKey List空间键
     * @param start   分页开始下标
     * @param end     分页结束下标
     * @param <T>     泛型类型
     * @return 截取的元素列表
     */
    public <T> List<T> range(final String listKey, long start, long end) {
        if (start < 0 || end < 0 || start > end) {
            throw new RedisException("分页参数错误！");
        }
        log.debug("List类型 range操作 listKey={} start={} end={}", listKey, start, end);
        return listOperations.range(listKey, start, --end);
    }

    /**
     * 操作listKey数据，分页截取listKey的元素，从右往左截取元素
     * 注意：
     *  1、截取元素包含开始下标start对应的元素
     *  2、截取元素不包含结束下标end对应的元素
     *  3、截取原则为‘左开右闭’
     * @param listKey List空间键
     * @param start   分页开始下标
     * @param end     分页结束下标
     * @param <T>     泛型类型
     * @return 截取的元素列表
     */
    public <T> List<T> rangeReverse(final String listKey, long start, long end) {
        if (start < 0 || end < 0 || start > end) {
            throw new RedisException("分页参数错误");
        }
        start = -start - 1;
        end = -end;
        log.debug("List类型 range逆序操作 listKey={} start={} end={}", listKey, end, start);
        return listOperations.range(listKey, end, start);   // 此处start和end均为负数，表示从右往左遍历截取元素
    }

    /**
     * 操作listKey数据，范围截取listKey的元素，从左往右截取元素，截取范围以外的元素全部删除
     * 注意：
     *  1、截取元素‘包含’开始下标start对应的元素
     *  2、截取元素’不包含‘结束下标end对应的元素
     *  3、截取原则为‘左开右闭’
     * @param listKey List空间键
     * @param start   分页开始下标
     * @param end     分页结束下标
     */
    public void trim(final String listKey, long start, long end) {
        if (start < 0 || end < 0 || start > end) {
            throw new RedisException("分页参数错误");
        }
        log.debug("List类型 trim操作 listKey={} start={} end={}", listKey, start, end);
        listOperations.trim(listKey, start, --end);
    }

    /**
     * 操作listKey数据，范围截取listKey的元素，从右往左截取元素，截取范围以外的元素全部删除
     * 注意：
     *  1、截取元素包含开始下标start对应的元素
     *  2、截取元素不包含结束下标end对应的元素
     *  3、截取原则为‘左开右闭’
     * @param listKey List空间键
     * @param start   分页开始下标
     * @param end     分页结束下标
     */
    public void trimReverse(final String listKey, long start, long end) {
        if (start < 0 || end < 0 || start > end) {
            throw new RedisException("分页参数错误");
        }
        start = -start - 1;
        end = -end;
        log.debug("List类型 trim逆序操作 listKey={} start={} end={}", listKey, end, start);
        listOperations.trim(listKey, end, start);   // 此处start和end均为负数，表示从右往左遍历裁剪元素
    }

    /**
     * 操作listKey数据，删除listKey中所有值等于value的元素
     * 注意：
     *  1、如果值value在listKey中不存在，则返回0（纯数字的值，区分数据类型，如Integer类型的数字不能匹配String类型的数字，反之同理）
     * @param listKey List空间键
     * @param value   待删除的值
     * @param <T>     泛型类型
     * @return 删除的元素个数
     */
    public <T> Long remove(final String listKey, final T value) {
        log.debug("List类型 remove操作 listKey={} value={}", listKey, value);
        return listOperations.remove(listKey, 0, value);
    }

    /**
     * 操作listKey数据，删除listKey中所有值等于value的元素，从startIndex下标开始匹配删除
     * 注意：
     *  1、如果值value在listKey中不存在，则返回0（纯数字的值，区分数据类型，如Integer类型的数字不能匹配String类型的数字，反之同理）
     * @param listKey    List空间键
     * @param startIndex 匹配删除开始下标
     * @param value      待删除的值
     * @param <T>        泛型类型
     * @return 删除的元素个数
     */
    public <T> Long remove(final String listKey, final long startIndex, final T value) {
        log.debug("List类型 remove操作 listKey={} startIndex={} value={}", listKey, startIndex, value);
        return listOperations.remove(listKey, startIndex, value);
    }

    public Long removeByIndex(final String listKey, final long index) {
        SessionCallback<Long> callback = new SessionCallback<Long>() {
            @Override
            public Long execute(RedisOperations redisOperations) throws DataAccessException {
                redisOperations.multi();

                listOperations.set(listKey, index, deleteFlag);     // 将待删除的元素替换成删除标记位
                listOperations.remove(listKey, index, deleteFlag);  // 将删除标记为删除

                List result = redisOperations.exec();
                return (Long) result.get(0);
            }
        };
        return (Long) redisTemplate.execute(callback);
    }
}
